home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / MDB / Manager.php < prev    next >
PHP Script  |  2004-03-24  |  105KB  |  2,146 lines

  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP Version 4                                                        |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1998-2004 Manuel Lemos, Tomas V.V.Cox,                 |
  6. // | Stig. S. Bakken, Lukas Smith                                         |
  7. // | All rights reserved.                                                 |
  8. // +----------------------------------------------------------------------+
  9. // | MDB is a merge of PEAR DB and Metabases that provides a unified DB   |
  10. // | API as well as database abstraction for PHP applications.            |
  11. // | This LICENSE is in the BSD license style.                            |
  12. // |                                                                      |
  13. // | Redistribution and use in source and binary forms, with or without   |
  14. // | modification, are permitted provided that the following conditions   |
  15. // | are met:                                                             |
  16. // |                                                                      |
  17. // | Redistributions of source code must retain the above copyright       |
  18. // | notice, this list of conditions and the following disclaimer.        |
  19. // |                                                                      |
  20. // | Redistributions in binary form must reproduce the above copyright    |
  21. // | notice, this list of conditions and the following disclaimer in the  |
  22. // | documentation and/or other materials provided with the distribution. |
  23. // |                                                                      |
  24. // | Neither the name of Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken,    |
  25. // | Lukas Smith nor the names of his contributors may be used to endorse |
  26. // | or promote products derived from this software without specific prior|
  27. // | written permission.                                                  |
  28. // |                                                                      |
  29. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
  30. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
  31. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
  32. // | FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE      |
  33. // | REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,          |
  34. // | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
  35. // | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS|
  36. // |  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED  |
  37. // | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT          |
  38. // | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY|
  39. // | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE          |
  40. // | POSSIBILITY OF SUCH DAMAGE.                                          |
  41. // +----------------------------------------------------------------------+
  42. // | Author: Lukas Smith <smith@backendmedia.com>                         |
  43. // +----------------------------------------------------------------------+
  44. //
  45. // $Id: Manager.php,v 1.75.4.2 2004/01/08 13:43:02 lsmith Exp $
  46. //
  47.  
  48. require_once('MDB/Parser.php');
  49.  
  50. define('MDB_MANAGER_DUMP_ALL',          0);
  51. define('MDB_MANAGER_DUMP_STRUCTURE',    1);
  52. define('MDB_MANAGER_DUMP_CONTENT',      2);
  53.  
  54. /**
  55.  * The database manager is a class that provides a set of database
  56.  * management services like installing, altering and dumping the data
  57.  * structures of databases.
  58.  *
  59.  * @package MDB
  60.  * @category Database
  61.  * @author  Lukas Smith <smith@backendmedia.com>
  62.  */
  63. class MDB_Manager extends PEAR
  64. {
  65.     // {{{ properties
  66.  
  67.     var $database;
  68.  
  69.     var $options = array(
  70.             'fail_on_invalid_names' => 1,
  71.             'debug' => 0
  72.         );
  73.     var $invalid_names = array(
  74.         'user' => array(),
  75.         'is' => array(),
  76.         'file' => array(
  77.             'oci' => array(),
  78.             'oracle' => array()
  79.         ),
  80.         'notify' => array(
  81.             'pgsql' => array()
  82.         ),
  83.         'restrict' => array(
  84.             'mysql' => array()
  85.         ),
  86.         'password' => array(
  87.             'ibase' => array()
  88.         )
  89.     );
  90.     var $default_values = array(
  91.         'integer' => 0,
  92.         'float' => 0,
  93.         'decimal' => 0,
  94.         'text' => '',
  95.         'timestamp' => '0001-01-01 00:00:00',
  96.         'date' => '0001-01-01',
  97.         'time' => '00:00:00'
  98.     );
  99.  
  100.     var $warnings = array();
  101.  
  102.     var $database_definition = array(
  103.         'name' => '',
  104.         'create' => 0,
  105.         'TABLES' => array()
  106.     );
  107.  
  108.     // }}}
  109.     // {{{ raiseError()
  110.  
  111.     /**
  112.      * This method is used to communicate an error and invoke error
  113.      * callbacks etc.  Basically a wrapper for PEAR::raiseError
  114.      * without the message string.
  115.      *
  116.      * @param mixed $code integer error code, or a PEAR error object (all
  117.      *      other parameters are ignored if this parameter is an object
  118.      * @param int $mode error mode, see PEAR_Error docs
  119.      * @param mixed $options If error mode is PEAR_ERROR_TRIGGER, this is the
  120.      *      error level (E_USER_NOTICE etc).  If error mode is
  121.      *      PEAR_ERROR_CALLBACK, this is the callback function, either as a
  122.      *      function name, or as an array of an object and method name. For
  123.      *      other error modes this parameter is ignored.
  124.      * @param string $userinfo Extra debug information.  Defaults to the last
  125.      *      query and native error code.
  126.      * @param mixed $nativecode Native error code, integer or string depending
  127.      *      the backend.
  128.      * @return object a PEAR error object
  129.      * @access public
  130.      * @see PEAR_Error
  131.      */
  132.     function &raiseError($code = MDB_MANAGER_ERROR, $mode = NULL, $options = NULL,
  133.         $userinfo = NULL, $nativecode = NULL)
  134.     {
  135.         // The error is yet a MDB error object
  136.         if(is_object($code)) {
  137.             $err = PEAR::raiseError($code, NULL, NULL, NULL, NULL, NULL, TRUE);
  138.             return($err);
  139.         }
  140.         
  141.         $err = PEAR::raiseError(NULL, $code, $mode, $options, $userinfo,
  142.             'MDB_Error', TRUE);
  143.         return($err);
  144.     }
  145.  
  146.     // }}}
  147.     // {{{ captureDebugOutput()
  148.  
  149.     /**
  150.      * set a debug handler
  151.      *
  152.      * @param string $capture name of the function that should be used in
  153.      *     debug()
  154.      * @access public
  155.      * @see debug()
  156.      */
  157.     function captureDebugOutput($capture)
  158.     {
  159.         $this->options['debug'] = $capture;
  160.         $this->database->captureDebugOutput(1);
  161.     }
  162.  
  163.     // }}}
  164.     // {{{ debugOutput()
  165.  
  166.     /**
  167.      * output debug info
  168.      *
  169.      * @return string content of the debug_output class variable
  170.      * @access public
  171.      */
  172.     function debugOutput()
  173.     {
  174.         return($this->database->debugOutput());
  175.     }
  176.  
  177.     // }}}
  178.     // {{{ resetWarnings()
  179.  
  180.     /**
  181.      * reset the warning array
  182.      *
  183.      * @access public
  184.      */
  185.     function resetWarnings()
  186.     {
  187.         $this->warnings = array();
  188.     }
  189.  
  190.     // }}}
  191.     // {{{ getWarnings()
  192.  
  193.     /**
  194.      * get all warnings in reverse order.
  195.      * This means that the last warning is the first element in the array
  196.      *
  197.      * @return array with warnings
  198.      * @access public
  199.      * @see resetWarnings()
  200.      */
  201.     function getWarnings()
  202.     {
  203.         return array_reverse($this->warnings);
  204.     }
  205.  
  206.     // }}}
  207.     // {{{ setOption()
  208.  
  209.     /**
  210.      * set the option for the db class
  211.      *
  212.      * @param string $option option name
  213.      * @param mixed $value value for the option
  214.      * @return mixed MDB_OK or MDB_Error
  215.      * @access public
  216.      */
  217.     function setOption($option, $value)
  218.     {
  219.         if(isset($this->options[$option])) {
  220.             $this->options[$option] = $value;
  221.             return(MDB_OK);
  222.         }
  223.         return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, "unknown option $option"));
  224.     }
  225.  
  226.     // }}}
  227.     // {{{ getOption()
  228.  
  229.     /**
  230.      * returns the value of an option
  231.      *
  232.      * @param string $option option name
  233.      * @return mixed the option value or error object
  234.      * @access public
  235.      */
  236.     function getOption($option)
  237.     {
  238.         if(isset($this->options[$option])) {
  239.             return($this->options[$option]);
  240.         }
  241.         return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL, "unknown option $option"));
  242.     }
  243.  
  244.     // }}}
  245.     // {{{ connect()
  246.  
  247.     /**
  248.      * Create a new MDB connection object and connect to the specified
  249.      * database
  250.      *
  251.      * @param   mixed   $dbinfo   'data source name', see the MDB::parseDSN
  252.      *                            method for a description of the dsn format.
  253.      *                            Can also be specified as an array of the
  254.      *                            format returned by MDB::parseDSN.
  255.      *                            Finally you can also pass an existing db
  256.      *                            object to be used.
  257.      * @param   mixed   $options  An associative array of option names and
  258.      *                            their values.
  259.      * @return  mixed MDB_OK on success, or a MDB error object
  260.      * @access  public
  261.      * @see     MDB::parseDSN
  262.      */
  263.     function &connect(&$dbinfo, $options = FALSE)
  264.     {
  265.         if(is_object($this->database) && !MDB::isError($this->database)) {
  266.             $this->disconnect();
  267.         }
  268.         if(is_object($dbinfo)) {
  269.              $this->database =& $dbinfo;
  270.         } else {
  271.             $this->database =& MDB::connect($dbinfo, $options);
  272.             if(MDB::isError($this->database)) {
  273.                 return($this->database);
  274.             }
  275.         }
  276.         if(is_array($options)) {
  277.             $this->options = array_merge($options, $this->options);
  278.         }
  279.         return(MDB_OK);
  280.     }
  281.  
  282.     // }}}
  283.     // {{{ disconnect()
  284.  
  285.     /**
  286.      * Log out and disconnect from the database.
  287.      *
  288.      * @access public
  289.      */
  290.     function disconnect()
  291.     {
  292.         if(is_object($this->database) && !MDB::isError($this->database)) {
  293.             $this->database->disconnect();
  294.             unset($this->database);
  295.         }
  296.     }
  297.  
  298.     // }}}
  299.     // {{{ setDatabase()
  300.  
  301.     /**
  302.      * Select a different database
  303.      *
  304.      * @param string $name name of the database that should be selected
  305.      * @return string name of the database previously connected to
  306.      * @access public
  307.      */
  308.     function setDatabase($name)
  309.     {
  310.         return($this->database->setDatabase($name));
  311.     }
  312.  
  313.     // }}}
  314.     // {{{ _createTable()
  315.  
  316.     /**
  317.      * create a table and inititialize the table if data is available
  318.      *
  319.      * @param string $table_name  name of the table to be created
  320.      * @param array  $table       multi dimensional array that containts the
  321.      *                            structure and optional data of the table
  322.      * @param boolean $overwrite  determine if the table/index should be
  323.                                   overwritten if it already exists
  324.      * @return mixed MDB_OK on success, or a MDB error object
  325.      * @access private
  326.      */
  327.     function _createTable($table_name, $table, $overwrite = FALSE)
  328.     {
  329.         $this->expectError(MDB_ERROR_ALREADY_EXISTS);
  330.         $result = $this->database->createTable($table_name, $table['FIELDS']);
  331.         $this->popExpect();
  332.         if(MDB::isError($result)) {
  333.             if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) {
  334.                 $this->warnings[] = 'Table already exists: '.$table_name;
  335.                 if($overwrite) {
  336.                     $this->database->debug('Overwritting Table');
  337.                     $result = $this->database->dropTable($table_name);
  338.                     if(MDB::isError($result)) {
  339.                         return($result);
  340.                     }
  341.                     $result = $this->database->createTable($table_name, $table['FIELDS']);
  342.                     if(MDB::isError($result)) {
  343.                         return($result);
  344.                     }
  345.                 } else {
  346.                     $result = MDB_OK;
  347.                 }
  348.             } else {
  349.                 $this->database->debug('Create table error: '.$table_name);
  350.                 return($result);
  351.             }
  352.         }
  353.         if(isset($table['initialization']) && is_array($table['initialization'])) {
  354.             foreach($table['initialization'] as $instruction) {
  355.                 switch($instruction['type']) {
  356.                     case 'insert':
  357.                         $query_fields = $query_values = array();
  358.                         if(isset($instruction['FIELDS']) && is_array($instruction['FIELDS'])) {
  359.                             foreach($instruction['FIELDS'] as $field_name => $field) {
  360.                                 $query_fields[] = $field_name;
  361.                                 $query_values[] = '?';
  362.                             }
  363.                             $query_fields = implode(',',$query_fields);
  364.                             $query_values = implode(',',$query_values);
  365.                             $result = $prepared_query = $this->database->prepareQuery(
  366.                                 "INSERT INTO $table_name ($query_fields) VALUES ($query_values)");
  367.                         }
  368.                         if(!MDB::isError($prepared_query)) {
  369.                             if(isset($instruction['FIELDS']) && is_array($instruction['FIELDS'])) {
  370.                                 $lobs = array();
  371.                                 $field_number = 0;
  372.                                 foreach($instruction['FIELDS'] as $field_name => $field) {
  373.                                     $field_number++;
  374.                                     $query = $field_name;
  375.                                     switch($table['FIELDS'][$field_name]['type']) {
  376.                                         case 'integer':
  377.                                             $result = $this->database->setParamInteger($prepared_query,
  378.                                                 $field_number, intval($field));
  379.                                             break;
  380.                                         case 'text':
  381.                                             $result = $this->database->setParamText($prepared_query,
  382.                                                 $field_number, $field);
  383.                                             break;
  384.                                         case 'clob':
  385.                                             $lob_definition = array(
  386.                                                 'Database' => $this->database,
  387.                                                 'Error' => '',
  388.                                                 'Data' => $field
  389.                                             );
  390.                                             if(MDB::isError($result = $this->database->createLob($lob_definition)))
  391.                                             {
  392.                                                 break;
  393.                                             }
  394.                                             $lob = count($lobs);
  395.                                             $lobs[$lob] = $result;
  396.                                             $result = $this->database->setParamClob($prepared_query,
  397.                                                 $field_number, $lobs[$lob], $field_name);
  398.                                             break;
  399.                                         case 'blob':
  400.                                             $lob_definition = array(
  401.                                                 'Database' => $this->database,
  402.                                                 'Error' => '',
  403.                                                 'Data' => $field
  404.                                             );
  405.                                             if(MDB::isError($result = $this->database->createLob($lob_definition))) {
  406.                                                 break;
  407.                                             }
  408.                                             $lob = count($lobs);
  409.                                             $lobs[$lob] = $result;
  410.                                             $result = $this->database->setParamBlob($prepared_query,
  411.                                                 $field_number, $lobs[$lob], $field_name);
  412.                                             break;
  413.                                         case 'boolean':
  414.                                             $result = $this->database->setParamBoolean($prepared_query,
  415.                                                 $field_number, intval($field));
  416.                                             break;
  417.                                         case 'date':
  418.                                             $result = $this->database->setParamDate($prepared_query,
  419.                                                 $field_number, $field);
  420.                                             break;
  421.                                         case 'timestamp':
  422.                                             $result = $this->database->setParamTimestamp($prepared_query,
  423.                                                 $field_number, $field);
  424.                                             break;
  425.                                         case 'time':
  426.                                             $result = $this->database->setParamTime($prepared_query,
  427.                                                 $field_number, $field);
  428.                                             break;
  429.                                         case 'float':
  430.                                             $result = $this->database->setParamFloat($prepared_query,
  431.                                                 $field_number, doubleval($field));
  432.                                             break;
  433.                                         case 'decimal':
  434.                                             $result = $this->database->setParamDecimal($prepared_query,
  435.                                                 $field_number, $field);
  436.                                             break;
  437.                                         default:
  438.                                             $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  439.                                                 'type "'.$field['type'].'" is not yet supported');
  440.                                             break;
  441.                                     }
  442.                                     if(MDB::isError($result)) {
  443.                                         break;
  444.                                     }
  445.                                 }
  446.                             }
  447.                             if(!MDB::isError($result)) {
  448.                                 $result = $this->database->executeQuery($prepared_query);
  449.                             }
  450.                             for($lob = 0; $lob < count($lobs); $lob++) {
  451.                                 $this->database->destroyLOB($lobs[$lob]);
  452.                             }
  453.                             $this->database->freePreparedQuery($prepared_query);
  454.                         }
  455.                         break;
  456.                 }
  457.             }
  458.         };
  459.         if(!MDB::isError($result) && isset($table['INDEXES']) && is_array($table['INDEXES'])) {
  460.             if(!$this->database->support('Indexes')) {
  461.                 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  462.                     'indexes are not supported'));
  463.             }
  464.             foreach($table['INDEXES'] as $index_name => $index) {
  465.                 $this->expectError(MDB_ERROR_ALREADY_EXISTS);
  466.                 $result = $this->database->createIndex($table_name, $index_name, $index);
  467.                 $this->popExpect();
  468.                 if(MDB::isError($result)) {
  469.                     if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) {
  470.                         $this->warnings[] = 'Index already exists: '.$index_name;
  471.                         if($overwrite) {
  472.                             $this->database->debug('Overwritting Index');
  473.                             $result = $this->database->dropIndex($table_name, $index_name);
  474.                             if(MDB::isError($result)) {
  475.                                 break;
  476.                             }
  477.                             $result = $this->database->createIndex($table_name, $index_name, $index);
  478.                             if(MDB::isError($result)) {
  479.                                 break;
  480.                             }
  481.                         } else {
  482.                             $result = MDB_OK;
  483.                         }
  484.                     } else {
  485.                         $this->database->debug('Create index error: '.$table_name);
  486.                         break;
  487.                     }
  488.                 }
  489.             }
  490.         }
  491.         if(MDB::isError($result)) {
  492.             $result = $this->database->dropTable($table_name);
  493.             if(MDB::isError($result)) {
  494.                 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  495.                     'could not drop the table ('
  496.                     .$result->getMessage().' ('.$result->getUserinfo(),'))',
  497.                     'MDB_Error', TRUE);
  498.             }
  499.             return($result);
  500.         }
  501.         return(MDB_OK);
  502.     }
  503.  
  504.     // }}}
  505.     // {{{ _dropTable()
  506.  
  507.     /**
  508.      * drop a table
  509.      *
  510.      * @param string $table_name    name of the table to be dropped
  511.      * @return mixed MDB_OK on success, or a MDB error object
  512.      * @access private
  513.      */
  514.     function _dropTable($table_name)
  515.     {
  516.         return($this->database->dropTable($table_name));
  517.     }
  518.  
  519.     // }}}
  520.     // {{{ _createSequence()
  521.  
  522.     /**
  523.      * create a sequence
  524.      *
  525.      * @param string $sequence_name  name of the sequence to be created
  526.      * @param array  $sequence       multi dimensional array that containts the
  527.      *                               structure and optional data of the table
  528.      * @param string $created_on_table
  529.      * @param boolean $overwrite    determine if the sequence should be overwritten
  530.                                     if it already exists
  531.      * @return mixed MDB_OK on success, or a MDB error object
  532.      * @access private
  533.      */
  534.     function _createSequence($sequence_name, $sequence, $created_on_table, $overwrite = FALSE)
  535.     {
  536.         if(!$this->database->support('Sequences')) {
  537.             return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  538.                 'sequences are not supported'));
  539.         }
  540.         if(!isset($sequence_name) || !strcmp($sequence_name, '')) {
  541.             return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  542.                 'no valid sequence name specified'));
  543.         }
  544.         $this->database->debug('Create sequence: '.$sequence_name);
  545.         if(isset($sequence['start']) && $sequence['start'] != '') {
  546.             $start = $sequence['start'];
  547.         } else if(isset($sequence['on']) && !$created_on_table) {
  548.             $table = $sequence['on']['table'];
  549.             $field = $sequence['on']['field'];
  550.             if($this->database->support('Summaryfunctions')) {
  551.                 $field = "MAX($field)";
  552.             }
  553.             $start = $this->database->queryOne("SELECT $field FROM $table");
  554.             if(MDB::isError($start)) {
  555.                 return($start);
  556.             }
  557.         } else {
  558.             $start = 1;
  559.         }
  560.         
  561.         $this->expectError(MDB_ERROR_ALREADY_EXISTS);
  562.         $result = $this->database->createSequence($sequence_name, $start);
  563.         $this->popExpect();
  564.         if(MDB::isError($result)) {
  565.             if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) {
  566.                 $this->warnings[] = 'Sequence already exists: '.$sequence_name;
  567.                 if($overwrite) {
  568.                     $this->database->debug('Overwritting Sequence');
  569.                     $result = $this->database->dropSequence($sequence_name);
  570.                     if(MDB::isError($result)) {
  571.                         return($result);
  572.                     }
  573.                     $result = $this->database->createSequence($sequence_name, $start);
  574.                     if(MDB::isError($result)) {
  575.                         return($result);
  576.                     }
  577.                 } else {
  578.                     return(MDB_OK);
  579.                 }
  580.             } else {
  581.                 $this->database->debug('Create sequence error: '.$sequence_name);
  582.                 return($result);
  583.             }
  584.         }
  585.     }
  586.  
  587.     // }}}
  588.     // {{{ _dropSequence()
  589.  
  590.     /**
  591.      * drop a table
  592.      *
  593.      * @param string $sequence_name    name of the sequence to be dropped
  594.      * @return mixed MDB_OK on success, or a MDB error object
  595.      * @access private
  596.      */
  597.     function _dropSequence($sequence_name)
  598.     {
  599.         if(!$this->database->support('Sequences')) {
  600.             return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  601.                 'sequences are not supported'));
  602.         }
  603.         $this->database->debug('Dropping sequence: '.$sequence_name);
  604.         if(!isset($sequence_name) || !strcmp($sequence_name, '')) {
  605.             return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  606.                 'no valid sequence name specified'));
  607.         }
  608.         return($this->database->dropSequence($sequence_name));
  609.     }
  610.  
  611.     // }}}
  612.     // {{{ _createDatabase()
  613.  
  614.     /**
  615.      * Create a database space within which may be created database objects
  616.      * like tables, indexes and sequences. The implementation of this function
  617.      * is highly DBMS specific and may require special permissions to run
  618.      * successfully. Consult the documentation or the DBMS drivers that you
  619.      * use to be aware of eventual configuration requirements.
  620.      *
  621.      * @return mixed MDB_OK on success, or a MDB error object
  622.      * @access private
  623.      */
  624.     function _createDatabase()
  625.     {
  626.         if(!isset($this->database_definition['name'])
  627.             || !strcmp($this->database_definition['name'], '')
  628.         ) {
  629.             return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  630.                 'no valid database name specified'));
  631.         }
  632.         $create = (isset($this->database_definition['create']) && $this->database_definition['create']);
  633.         $overwrite = (isset($this->database_definition['overwrite']) && $this->database_definition['overwrite']);
  634.         if($create) {
  635.             $this->database->debug('Create database: '.$this->database_definition['name']);
  636.             $this->expectError(MDB_ERROR_ALREADY_EXISTS);
  637.             $result = $this->database->createDatabase($this->database_definition['name']);
  638.             $this->popExpect();
  639.             if(MDB::isError($result)) {
  640.                 if($result->getCode() === MDB_ERROR_ALREADY_EXISTS) {
  641.                     $this->warnings[] = 'Database already exists: '.$this->database_definition['name'];
  642.                     if($overwrite) {
  643.                         $this->database->debug('Overwritting Database');
  644.                         $result = $this->database->dropDatabase($this->database_definition['name']);
  645.                         if(MDB::isError($result)) {
  646.                             return($result);
  647.                         }
  648.                         $result = $this->database->createDatabase($this->database_definition['name']);
  649.                         if(MDB::isError($result)) {
  650.                             return($result);
  651.                         }
  652.                     } else {
  653.                         $result = MDB_OK;
  654.                     }
  655.                 } else {
  656.                     $this->database->debug('Create database error.');
  657.                     return($result);
  658.                 }
  659.             }
  660.         }
  661.         $previous_database_name = $this->database->setDatabase($this->database_definition['name']);
  662.         if(($support_transactions = $this->database->support('Transactions'))
  663.             && MDB::isError($result = $this->database->autoCommit(FALSE))
  664.         ) {
  665.             return($result);
  666.         }
  667.  
  668.         $created_objects = 0;
  669.         if(isset($this->database_definition['TABLES'])
  670.             && is_array($this->database_definition['TABLES'])
  671.         ) {
  672.             foreach($this->database_definition['TABLES'] as $table_name => $table) {
  673.                 $result = $this->_createTable($table_name, $table, $overwrite);
  674.                 if(MDB::isError($result)) {
  675.                     break;
  676.                 }
  677.                 $created_objects++;
  678.             }
  679.         }
  680.         if(!MDB::isError($result) 
  681.             && isset($this->database_definition['SEQUENCES'])
  682.             && is_array($this->database_definition['SEQUENCES'])
  683.         ) {
  684.             foreach($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) {
  685.                 $result = $this->_createSequence($sequence_name, $sequence, 0, $overwrite);
  686.                 
  687.                 if(MDB::isError($result)) {
  688.                     break;
  689.                 }
  690.                 $created_objects++;
  691.             }
  692.         }
  693.         
  694.         if(MDB::isError($result)) {
  695.             if($created_objects) {
  696.                 if($support_transactions) {
  697.                     $res = $this->database->rollback();
  698.                     if(MDB::isError($res))
  699.                         $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  700.                             'Could not rollback the partially created database alterations ('
  701.                             .$result->getMessage().' ('.$result->getUserinfo(),'))',
  702.                             'MDB_Error', TRUE);
  703.                 } else {
  704.                     $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  705.                         'the database was only partially created ('
  706.                         .$result->getMessage().' ('.$result->getUserinfo(),'))',
  707.                         'MDB_Error', TRUE);
  708.                 }
  709.             }
  710.         } else {
  711.             if($support_transactions) {
  712.                 $res = $this->database->autoCommit(TRUE);
  713.                 if(MDB::isError($res))
  714.                     $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  715.                         'Could not end transaction after successfully created the database ('
  716.                         .$res->getMessage().' ('.$res->getUserinfo(),'))',
  717.                         'MDB_Error', TRUE);
  718.             }
  719.         }
  720.         
  721.         $this->database->setDatabase($previous_database_name);
  722.         
  723.         if(MDB::isError($result)
  724.             && $create
  725.             && MDB::isError($res = $this->database->dropDatabase($this->database_definition['name']))
  726.         ) {
  727.             return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  728.                 'Could not drop the created database after unsuccessful creation attempt ('
  729.                 .$res->getMessage().' ('.$res->getUserinfo(),'))',
  730.                 'MDB_Error', TRUE));
  731.         }
  732.         
  733.         if(MDB::isError($result)) {
  734.             return($result);
  735.         }
  736.         
  737.         return(MDB_OK);
  738.     }
  739.  
  740.     // }}}
  741.     // {{{ _addDefinitionChange()
  742.  
  743.     /**
  744.      * add change to an array of multiple changes
  745.      *
  746.      * @param array  &$changes
  747.      * @param string $definition
  748.      * @param string $item
  749.      * @param array  $change
  750.      * @return mixed MDB_OK on success, or a MDB error object
  751.      * @access private
  752.      */
  753.     function _addDefinitionChange(&$changes, $definition, $item, $change)
  754.     {
  755.         if(!isset($changes[$definition][$item])) {
  756.             $changes[$definition][$item] = array();
  757.         }
  758.         foreach($change as $change_data_name => $change_data) {
  759.             if(isset($change_data) && is_array($change_data)) {
  760.                 if(!isset($changes[$definition][$item][$change_data_name])) {
  761.                     $changes[$definition][$item][$change_data_name] = array();
  762.                 }
  763.                 foreach($change_data as $change_part_name => $change_part) {
  764.                     $changes[$definition][$item][$change_data_name][$change_part_name] = $change_part;
  765.                 }
  766.             } else {
  767.                 $changes[$definition][$item][$change_data_name] = $change_data;
  768.             }
  769.         }
  770.         return(MDB_OK);
  771.     }
  772.  
  773.     // }}}
  774.     // {{{ _compareDefinitions()
  775.  
  776.     /**
  777.      * compare a previous definition with the currenlty parsed definition
  778.      *
  779.      * @param array multi dimensional array that contains the previous definition
  780.      * @return mixed array of changes on success, or a MDB error object
  781.      * @access private
  782.      */
  783.     function _compareDefinitions($previous_definition)
  784.     {
  785.         $defined_tables = $changes = array();
  786.         if(isset($this->database_definition['TABLES']) && is_array($this->database_definition['TABLES'])) {
  787.             foreach($this->database_definition['TABLES'] as $table_name => $table) {
  788.                 $was_table_name = $table['was'];
  789.                 if(isset($previous_definition['TABLES'][$table_name])
  790.                     && isset($previous_definition['TABLES'][$table_name]['was'])
  791.                     && !strcmp($previous_definition['TABLES'][$table_name]['was'], $was_table_name)
  792.                 ) {
  793.                     $was_table_name = $table_name;
  794.                 }
  795.                 if(isset($previous_definition['TABLES'][$was_table_name])) {
  796.                     if(strcmp($was_table_name, $table_name)) {
  797.                         $this->_addDefinitionChange($changes, 'TABLES', $was_table_name, array('name' => $table_name));
  798.                         $this->database->debug("Renamed table '$was_table_name' to '$table_name'");
  799.                     }
  800.                     if(isset($defined_tables[$was_table_name])) {
  801.                         return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  802.                             'the table "'.$was_table_name.'" was specified as base of more than of table of the database',
  803.                             'MDB_Error', TRUE));
  804.                     }
  805.                     $defined_tables[$was_table_name] = 1;
  806.                     
  807.                     $previous_fields = $previous_definition['TABLES'][$was_table_name]['FIELDS'];
  808.                     $defined_fields = array();
  809.                     if(isset($table['FIELDS']) && is_array($table['FIELDS'])) {
  810.                         foreach($table['FIELDS'] as $field_name => $field) {
  811.                             $was_field_name = $field['was'];
  812.                             if(isset($previous_fields[$field_name])
  813.                                 && isset($previous_fields[$field_name]['was'])
  814.                                 && !strcmp($previous_fields[$field_name]['was'], $was_field_name)
  815.                             ) {
  816.                                 $was_field_name = $field_name;
  817.                             }
  818.                             if(isset($previous_fields[$was_field_name])) {
  819.                                 if(strcmp($was_field_name, $field_name)) {
  820.                                     $query = $this->database->getFieldDeclaration($field_name, $field);
  821.                                     if(MDB::isError($query)) {
  822.                                         return($query);
  823.                                     }
  824.                                     $this->_addDefinitionChange($changes, 'TABLES', $was_table_name,
  825.                                         array(
  826.                                             'RenamedFields' => array(
  827.                                                 $was_field_name => array(
  828.                                                     'name' => $field_name,
  829.                                                     'Declaration' => $query
  830.                                                 )
  831.                                             )
  832.                                         )
  833.                                     );
  834.                                     $this->database->debug("Renamed field '$was_field_name' to '$field_name' in table '$table_name'");
  835.                                 }
  836.                                 if(isset($defined_fields[$was_field_name])) {
  837.                                     return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  838.                                         'the field "'.$was_table_name.'" was specified as base of more than one field of table',
  839.                                         'MDB_Error', TRUE));
  840.                                 }
  841.                                 $defined_fields[$was_field_name] = 1;
  842.                                 $change = array();
  843.                                 if($field['type'] == $previous_fields[$was_field_name]['type']) {
  844.                                     switch($field['type']) {
  845.                                         case 'integer':
  846.                                             $previous_unsigned = isset($previous_fields[$was_field_name]['unsigned']);
  847.                                             $unsigned = isset($fields[$field_name]['unsigned']);
  848.                                             if(strcmp($previous_unsigned, $unsigned)) {
  849.                                                 $change['unsigned'] = $unsigned;
  850.                                                 $this->database->debug("Changed field '$field_name' type from '".($previous_unsigned ? 'unsigned ' : '').$previous_fields[$was_field_name]['type']."' to '".($unsigned ? 'unsigned ' : '').$field['type']."' in table '$table_name'");
  851.                                             }
  852.                                             break;
  853.                                         case 'text':
  854.                                         case 'clob':
  855.                                         case 'blob':
  856.                                             $previous_length = (isset($previous_fields[$was_field_name]['length']) ? $previous_fields[$was_field_name]['length'] : 0);
  857.                                             $length = (isset($field['length']) ? $field['length'] : 0);
  858.                                             if(strcmp($previous_length, $length)) {
  859.                                                 $change['length'] = $length;
  860.                                                 $this->database->debug("Changed field '$field_name' length from '".$previous_fields[$was_field_name]['type'].($previous_length == 0 ? ' no length' : "($previous_length)")."' to '".$field['type'].($length == 0 ? ' no length' : "($length)")."' in table '$table_name'");
  861.                                             }
  862.                                             break;
  863.                                         case 'date':
  864.                                         case 'timestamp':
  865.                                         case 'time':
  866.                                         case 'boolean':
  867.                                         case 'float':
  868.                                         case 'decimal':
  869.                                             break;
  870.                                         default:
  871.                                             return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  872.                                                 'type "'.$field['type'].'" is not yet supported',
  873.                                                 'MDB_Error', TRUE));
  874.                                     }
  875.                                     
  876.                                     $previous_notnull = isset($previous_fields[$was_field_name]['notnull']);
  877.                                     $notnull = isset($field['notnull']);
  878.                                     if($previous_notnull != $notnull) {
  879.                                         $change['ChangedNotNull'] = 1;
  880.                                         if($notnull) {
  881.                                             $change['notnull'] = isset($field['notnull']);
  882.                                         }
  883.                                         $this->database->debug("Changed field '$field_name' notnull from $previous_notnull to $notnull in table '$table_name'");
  884.                                     }
  885.                                     
  886.                                     $previous_default = isset($previous_fields[$was_field_name]['default']);
  887.                                     $default = isset($field['default']);
  888.                                     if(strcmp($previous_default, $default)) {
  889.                                         $change['ChangedDefault'] = 1;
  890.                                         if($default) {
  891.                                             $change['default'] = $field['default'];
  892.                                         }
  893.                                         $this->database->debug("Changed field '$field_name' default from ".($previous_default ? "'".$previous_fields[$was_field_name]['default']."'" : 'NULL').' TO '.($default ? "'".$fields[$field_name]['default']."'" : 'NULL')." IN TABLE '$table_name'");
  894.                                     } else {
  895.                                         if($default
  896.                                             && strcmp($previous_fields[$was_field_name]['default'], $field['default'])
  897.                                         ) {
  898.                                             $change['ChangedDefault'] = 1;
  899.                                             $change['default'] = $field['default'];
  900.                                             $this->database->debug("Changed field '$field_name' default from '".$previous_fields[$was_field_name]['default']."' to '".$fields[$field_name]['default']."' in table '$table_name'");
  901.                                         }
  902.                                     }
  903.                                 } else {
  904.                                     $change['type'] = $field['type'];
  905.                                     $this->database->debug("Changed field '$field_name' type from '".$previous_fields[$was_field_name]['type']."' to '".$fields[$field_name]['type']."' in table '$table_name'");
  906.                                 }
  907.                                 if(count($change)) {
  908.                                     $query = $this->database->getFieldDeclaration($field_name, $field);
  909.                                     if(MDB::isError($query)) {
  910.                                         return($query);
  911.                                     }
  912.                                     $change['Declaration'] = $query;
  913.                                     $change['Definition'] = $field;
  914.                                     $this->_addDefinitionChange($changes, 'TABLES', $was_table_name, array('ChangedFields' => array($field_name => $change)));
  915.                                 }
  916.                             } else {
  917.                                 if(strcmp($field_name, $was_field_name)) {
  918.                                     return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  919.                                         'it was specified a previous field name ("'
  920.                                         .$was_field_name.'") for field "'.$field_name.'" of table "'
  921.                                         .$table_name.'" that does not exist',
  922.                                         'MDB_Error', TRUE));
  923.                                 }
  924.                                 $query = $this->database->getFieldDeclaration($field_name, $field);
  925.                                 if(MDB::isError($query)) {
  926.                                     return($query);
  927.                                 }
  928.                                 $change['Declaration'] = $query;
  929.                                 $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('AddedFields' => array($field_name => $change)));
  930.                                 $this->database->debug("Added field '$field_name' to table '$table_name'");
  931.                             }
  932.                         }
  933.                     }
  934.                     if(isset($previous_fields) && is_array($previous_fields)) {
  935.                         foreach ($previous_fields as $field_previous_name => $field_previous) {
  936.                             if(!isset($defined_fields[$field_previous_name])) {
  937.                                 $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('RemovedFields' => array($field_previous_name => array())));
  938.                                 $this->database->debug("Removed field '$field_name' from table '$table_name'");
  939.                             }
  940.                         }
  941.                     }
  942.                     $indexes = array();
  943.                     if(isset($this->database_definition['TABLES'][$table_name]['INDEXES'])
  944.                         && is_array($this->database_definition['TABLES'][$table_name]['INDEXES'])
  945.                     ) {
  946.                         $indexes = $this->database_definition['TABLES'][$table_name]['INDEXES'];
  947.                     }
  948.                     $previous_indexes = array();
  949.                     if(isset($previous_definition['TABLES'][$was_table_name]['INDEXES'])
  950.                         && is_array($previous_definition['TABLES'][$was_table_name]['INDEXES'])
  951.                     ) {
  952.                         $previous_indexes = $previous_definition['TABLES'][$was_table_name]['INDEXES'];
  953.                     }
  954.                     $defined_indexes = array();
  955.                     foreach($indexes as $index_name => $index) {
  956.                         $was_index_name = $index['was'];
  957.                         if(isset($previous_indexes[$index_name])
  958.                             && isset($previous_indexes[$index_name]['was'])
  959.                             && !strcmp($previous_indexes[$index_name]['was'], $was_index_name)
  960.                         ) {
  961.                             $was_index_name = $index_name;
  962.                         }
  963.                         if(isset($previous_indexes[$was_index_name])) {
  964.                             $change = array();
  965.                             
  966.                             if(strcmp($was_index_name, $index_name)) {
  967.                                 $change['name'] = $was_index_name;
  968.                                 $this->database->debug("Changed index '$was_index_name' name to '$index_name' in table '$table_name'");
  969.                             }
  970.                             if(isset($defined_indexes[$was_index_name])) {
  971.                                 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  972.                                     'the index "'.$was_index_name.'" was specified as base of'
  973.                                     .' more than one index of table "'.$table_name.'"',
  974.                                     'MDB_Error', TRUE));
  975.                             }
  976.                             $defined_indexes[$was_index_name] = 1;
  977.                             
  978.                             $previous_unique = isset($previous_indexes[$was_index_name]['unique']);
  979.                             $unique = isset($index['unique']);
  980.                             if($previous_unique != $unique) {
  981.                                 $change['ChangedUnique'] = 1;
  982.                                 if($unique) {
  983.                                     $change['unique'] = $unique;
  984.                                 }
  985.                                 $this->database->debug("Changed index '$index_name' unique from $previous_unique to $unique in table '$table_name'");
  986.                             }
  987.                             $defined_fields = array();
  988.                             $previous_fields = $previous_indexes[$was_index_name]['FIELDS'];
  989.                             if(isset($index['FIELDS']) && is_array($index['FIELDS'])) {
  990.                                 foreach($index['FIELDS'] as $field_name => $field) {
  991.                                     if(isset($previous_fields[$field_name])) {
  992.                                         $defined_fields[$field_name] = 1;
  993.                                         $sorting = (isset($field['sorting']) ? $field['sorting'] : '');
  994.                                         $previous_sorting = (isset($previous_fields[$field_name]['sorting']) ? $previous_fields[$field_name]['sorting'] : '');
  995.                                         if(strcmp($sorting, $previous_sorting)) {
  996.                                             $this->database->debug("Changed index field '$field_name' sorting default from '$previous_sorting' to '$sorting' in table '$table_name'");
  997.                                             $change['ChangedFields'] = 1;
  998.                                         }
  999.                                     } else {
  1000.                                         $change['ChangedFields'] = 1;
  1001.                                         $this->database->debug("Added field '$field_name' to index '$index_name' of table '$table_name'");
  1002.                                     }
  1003.                                 }
  1004.                             }
  1005.                             if(isset($previous_fields) && is_array($previous_fields)) {
  1006.                                 foreach($previous_fields as $field_name => $field) {
  1007.                                     if(!isset($defined_fields[$field_name])) {
  1008.                                         $change['ChangedFields'] = 1;
  1009.                                         $this->database->debug("Removed field '$field_name' from index '$index_name' of table '$table_name'");
  1010.                                     }
  1011.                                 }
  1012.                             }
  1013.                             
  1014.                             if(count($change)) {
  1015.                                 $this->_addDefinitionChange($changes, 'INDEXES', $table_name,array('ChangedIndexes' => array($index_name => $change)));
  1016.                             }
  1017.                         } else {
  1018.                             if(strcmp($index_name, $was_index_name)) {
  1019.                                 return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  1020.                                     'it was specified a previous index name ("'.$was_index_name
  1021.                                     .') for index "'.$index_name.'" of table "'.$table_name.'" that does not exist',
  1022.                                     'MDB_Error', TRUE));
  1023.                             }
  1024.                             $this->_addDefinitionChange($changes, 'INDEXES', $table_name,array('AddedIndexes' => array($index_name => $indexes[$index_name])));
  1025.                             $this->database->debug("Added index '$index_name' to table '$table_name'");
  1026.                         }
  1027.                     }
  1028.                     foreach($previous_indexes as $index_previous_name => $index_previous) {
  1029.                         if(!isset($defined_indexes[$index_previous_name])) {
  1030.                             $this->_addDefinitionChange($changes, 'INDEXES', $table_name, array('RemovedIndexes' => array($index_previous_name => $was_table_name)));
  1031.                             $this->database->debug("Removed index '$index_name' from table '$table_name'");
  1032.                         }
  1033.                     }
  1034.                 } else {
  1035.                     if(strcmp($table_name, $was_table_name)) {
  1036.                         return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  1037.                             'it was specified a previous table name ("'
  1038.                             .$was_table_name.'") for table "'.$table_name.'" that does not exist',
  1039.                             'MDB_Error', TRUE));
  1040.                     }
  1041.                     $this->_addDefinitionChange($changes, 'TABLES', $table_name,array('Add' => 1));
  1042.                     $this->database->debug("Added table '$table_name'");
  1043.                 }
  1044.             }
  1045.             if(isset($previous_definition['TABLES']) && is_array($previous_definition['TABLES'])) {
  1046.                 foreach ($previous_definition['TABLES'] as $table_name => $table) {
  1047.                     if(!isset($defined_tables[$table_name])) {
  1048.                         $this->_addDefinitionChange($changes, 'TABLES', $table_name, array('Remove' => 1));
  1049.                         $this->database->debug("Removed table '$table_name'");
  1050.                     }
  1051.                 }
  1052.             }
  1053.             if(isset($this->database_definition['SEQUENCES']) && is_array($this->database_definition['SEQUENCES'])) {
  1054.                 foreach ($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) {
  1055.                     $was_sequence_name = $sequence['was'];
  1056.                     if(isset($previous_definition['SEQUENCES'][$sequence_name])
  1057.                         && isset($previous_definition['SEQUENCES'][$sequence_name]['was'])
  1058.                         && !strcmp($previous_definition['SEQUENCES'][$sequence_name]['was'], $was_sequence_name)
  1059.                     ) {
  1060.                         $was_sequence_name = $sequence_name;
  1061.                     }
  1062.                     if(isset($previous_definition['SEQUENCES'][$was_sequence_name])) {
  1063.                         if(strcmp($was_sequence_name, $sequence_name)) {
  1064.                             $this->_addDefinitionChange($changes, 'SEQUENCES', $was_sequence_name,array('name' => $sequence_name));
  1065.                             $this->database->debug("Renamed sequence '$was_sequence_name' to '$sequence_name'");
  1066.                         }
  1067.                         if(isset($defined_sequences[$was_sequence_name])) {
  1068.                             return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  1069.                                 'the sequence "'.$was_sequence_name.'" was specified as base'
  1070.                                 .' of more than of sequence of the database',
  1071.                                 'MDB_Error', TRUE));
  1072.                         }
  1073.                         $defined_sequences[$was_sequence_name] = 1;
  1074.                         $change = array();
  1075.                         if(strcmp($sequence['start'], $previous_definition['SEQUENCES'][$was_sequence_name]['start'])) {
  1076.                             $change['start'] = $this->database_definition['SEQUENCES'][$sequence_name]['start'];
  1077.                             $this->database->debug("Changed sequence '$sequence_name' start from '".$previous_definition['SEQUENCES'][$was_sequence_name]['start']."' to '".$this->database_definition['SEQUENCES'][$sequence_name]['start']."'");
  1078.                         }
  1079.                         if(strcmp($sequence['on']['table'], $previous_definition['SEQUENCES'][$was_sequence_name]['on']['table'])
  1080.                             || strcmp($sequence['on']['field'], $previous_definition['SEQUENCES'][$was_sequence_name]['on']['field'])
  1081.                         ) {
  1082.                             $change['on'] = $sequence['on'];
  1083.                             $this->database->debug("Changed sequence '$sequence_name' on table field from '".$previous_definition['SEQUENCES'][$was_sequence_name]['on']['table'].'.'.$previous_definition['SEQUENCES'][$was_sequence_name]['on']['field']."' to '".$this->database_definition['SEQUENCES'][$sequence_name]['on']['table'].'.'.$this->database_definition['SEQUENCES'][$sequence_name]['on']['field']."'");
  1084.                         }
  1085.                         if(count($change)) {
  1086.                             $this->_addDefinitionChange($changes, 'SEQUENCES', $was_sequence_name,array('Change' => array($sequence_name => array($change))));
  1087.                         }
  1088.                     } else {
  1089.                         if(strcmp($sequence_name, $was_sequence_name)) {
  1090.                             return($this->raiseError(MDB_ERROR_INVALID, NULL, NULL,
  1091.                                 'it was specified a previous sequence name ("'.$was_sequence_name
  1092.                                 .'") for sequence "'.$sequence_name.'" that does not exist',
  1093.                                 'MDB_Error', TRUE));
  1094.                         }
  1095.                         $this->_addDefinitionChange($changes, 'SEQUENCES', $sequence_name, array('Add' => 1));
  1096.                         $this->database->debug("Added sequence '$sequence_name'");
  1097.                     }
  1098.                 }
  1099.             }
  1100.             if(isset($previous_definition['SEQUENCES']) && is_array($previous_definition['SEQUENCES'])) {
  1101.                 foreach ($previous_definition['SEQUENCES'] as $sequence_name => $sequence) {
  1102.                     if(!isset($defined_sequences[$sequence_name])) {
  1103.                         $this->_addDefinitionChange($changes, 'SEQUENCES', $sequence_name, array('Remove' => 1));
  1104.                         $this->database->debug("Removed sequence '$sequence_name'");
  1105.                     }
  1106.                 }
  1107.             }
  1108.         }
  1109.         return($changes);
  1110.     }
  1111.  
  1112.     // }}}
  1113.     // {{{ _alterDatabase()
  1114.  
  1115.     /**
  1116.      * Execute the necessary actions to implement the requested changes
  1117.      * in a database structure.
  1118.      *
  1119.      * @param array $previous_definition an associative array that contains
  1120.      * the definition of the database structure before applying the requested
  1121.      * changes. The definition of this array may be built separately, but
  1122.      * usually it is built by the Parse method the Metabase parser class.
  1123.      * @param array $changes an associative array that contains the definition of
  1124.      * the changes that are meant to be applied to the database structure.
  1125.      * @return mixed MDB_OK on success, or a MDB error object
  1126.      * @access private
  1127.      */
  1128.     function _alterDatabase($previous_definition, $changes)
  1129.     {
  1130.         $result = '';
  1131.         if(isset($changes['TABLES']) && is_array($changes['TABLES'])) {
  1132.             foreach($changes['TABLES'] as $table_name => $table) {
  1133.                 if(isset($table['Add']) || isset($table['Remove'])) {
  1134.                     continue;
  1135.                 }
  1136.                 $result = $this->database->alterTable($table_name, $table, 1);
  1137.                 if(MDB::isError($result)) {
  1138.                     return($result);
  1139.                 }
  1140.             }
  1141.         }
  1142.         if(isset($changes['SEQUENCES']) && is_array($changes['SEQUENCES'])) {
  1143.             if(!$this->database->support('Sequences')) {
  1144.                 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1145.                     'sequences are not supported'));
  1146.             }
  1147.             foreach($changes['SEQUENCES'] as $sequence) {
  1148.                 if(isset($sequence['Add'])
  1149.                     || isset($sequence['Remove'])
  1150.                     || isset($sequence['Change'])
  1151.                 ) {
  1152.                     continue;
  1153.                 }
  1154.                 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1155.                     'some sequences changes are not yet supported'));
  1156.             }
  1157.         }
  1158.         if(isset($changes['INDEXES']) && is_array($changes['INDEXES'])) {
  1159.             if(!$this->database->support('Indexes')) {
  1160.                 return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1161.                     'indexes are not supported'));
  1162.             }
  1163.             foreach($changes['INDEXES'] as $index) {
  1164.                 $table_changes = count($index);
  1165.                 if(isset($index['AddedIndexes'])) {
  1166.                     $table_changes--;
  1167.                 }
  1168.                 if(isset($index['RemovedIndexes'])) {
  1169.                     $table_changes--;
  1170.                 }
  1171.                 if(isset($index['ChangedIndexes'])) {
  1172.                     $table_changes--;
  1173.                 }
  1174.                 if($table_changes) {
  1175.                     return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1176.                         'index alteration not yet supported'));
  1177.                 }
  1178.             }
  1179.         }
  1180.         
  1181.         $previous_database_name = $this->database->setDatabase($this->database_definition['name']);
  1182.         if(($support_transactions = $this->database->support('Transactions'))
  1183.             && MDB::isError($result = $this->database->autoCommit(FALSE))
  1184.         ) {
  1185.             return($result);
  1186.         }
  1187.         $error = '';
  1188.         $alterations = 0;
  1189.         if(isset($changes['INDEXES']) && is_array($changes['INDEXES'])) {
  1190.             foreach($changes['INDEXES'] as $index_name => $index) {
  1191.                 if(isset($index['RemovedIndexes']) && is_array($index['RemovedIndexes'])) {
  1192.                     foreach($index['RemovedIndexes'] as $index_remove_name => $index_remove) {
  1193.                         $result = $this->database->dropIndex($index_name,$index_remove_name);
  1194.                         if(MDB::isError($result)) {
  1195.                             break;
  1196.                         }
  1197.                         $alterations++;
  1198.                     }
  1199.                 }
  1200.                 if(!MDB::isError($result)
  1201.                     && is_array($index['ChangedIndexes'])
  1202.                 ) {
  1203.                     foreach($index['ChangedIndexes'] as $index_changed_name => $index_changed) {
  1204.                         $was_name = (isset($indexes[$name]['name']) ? $indexes[$index_changed_name]['name'] : $index_changed_name);
  1205.                         $result = $this->database->dropIndex($index_name, $was_name);
  1206.                         if(MDB::isError($result)) {
  1207.                             break;
  1208.                         }
  1209.                         $alterations++;
  1210.                     }
  1211.                 }
  1212.                 if(MDB::isError($result)) {
  1213.                     break;
  1214.                 }
  1215.             }
  1216.         }
  1217.         if(!MDB::isError($result) && isset($changes['TABLES'])
  1218.             && is_array($changes['TABLES'])
  1219.         ) {
  1220.             foreach($changes['TABLES'] as $table_name => $table) {
  1221.                 if(isset($table['Remove'])) {
  1222.                     $result = $this->_dropTable($table_name);
  1223.                     if(!MDB::isError($result)) {
  1224.                         $alterations++;
  1225.                     }
  1226.                 } else {
  1227.                     if(!isset($table['Add'])) {
  1228.                         $result = $this->database->alterTable($table_name, $changes['TABLES'][$table_name], 0);
  1229.                         if(!MDB::isError($result)) {
  1230.                             $alterations++;
  1231.                         }
  1232.                     }
  1233.                 }
  1234.                 if(MDB::isError($result)) {
  1235.                     break;
  1236.                 }
  1237.             }
  1238.             foreach($changes['TABLES'] as $table_name => $table) {
  1239.                 if(isset($table['Add'])) {
  1240.                     $result = $this->_createTable($table_name, $this->database_definition['TABLES'][$table_name]);
  1241.                     if(!MDB::isError($result)) {
  1242.                         $alterations++;
  1243.                     }
  1244.                 }
  1245.                 if(MDB::isError($result)) {
  1246.                     break;
  1247.                 }
  1248.             }
  1249.         }
  1250.         if(!MDB::isError($result) && isset($changes['SEQUENCES']) && is_array($changes['SEQUENCES'])) {
  1251.             foreach($changes['SEQUENCES'] as $sequence_name => $sequence) {
  1252.                 if(isset($sequence['Add'])) {
  1253.                     $created_on_table = 0;
  1254.                     if(isset($this->database_definition['SEQUENCES'][$sequence_name]['on'])) {
  1255.                         $table = $this->database_definition['SEQUENCES'][$sequence_name]['on']['table'];
  1256.                         if(isset($changes['TABLES'])
  1257.                             && isset($changes['TABLES'][$table_name])
  1258.                             && isset($changes['TABLES'][$table_name]['Add'])
  1259.                         ) {
  1260.                             $created_on_table = 1;
  1261.                         }
  1262.                     }
  1263.                     
  1264.                     $result = $this->_createSequence($sequence_name,
  1265.                         $this->database_definition['SEQUENCES'][$sequence_name], $created_on_table);
  1266.                     if(!MDB::isError($result)) {
  1267.                         $alterations++;
  1268.                     }
  1269.                 } else {
  1270.                     if(isset($sequence['Remove'])) {
  1271.                         if(!strcmp($error = $this->_dropSequence($sequence_name), '')) {
  1272.                             $alterations++;
  1273.                         }
  1274.                     } else {
  1275.                         if(isset($sequence['Change'])) {
  1276.                             $created_on_table = 0;
  1277.                             if(isset($this->database_definition['SEQUENCES'][$sequence_name]['on'])) {
  1278.                                 $table = $this->database_definition['SEQUENCES'][$sequence_name]['on']['table'];
  1279.                                 if(isset($changes['TABLES'])
  1280.                                     && isset($changes['TABLES'][$table_name])
  1281.                                     && isset($changes['TABLES'][$table_name]['Add'])
  1282.                                 ) {
  1283.                                     $created_on_table = 1;
  1284.                                 }
  1285.                             }
  1286.                             if(!MDB::isError($result = $this->_dropSequence(
  1287.                                     $this->database_definition['SEQUENCES'][$sequence_name]['was']), '')
  1288.                                 && !MDB::isError($result = $this->_createSequence(
  1289.                                     $sequence_name, $this->database_definition['SEQUENCES'][$sequence_name], $created_on_table), '')
  1290.                             ) {
  1291.                                 $alterations++;
  1292.                             }
  1293.                         } else {
  1294.                             return($this->raiseError(MDB_ERROR_UNSUPPORTED, NULL, NULL,
  1295.                                 'changing sequences is not yet supported'));
  1296.                         }
  1297.                     }
  1298.                 }
  1299.                 if(MDB::isError($result)) {
  1300.                     break;
  1301.                 }
  1302.             }
  1303.         }
  1304.         if(!MDB::isError($result) && isset($changes['INDEXES']) && is_array($changes['INDEXES'])) {
  1305.             foreach($changes['INDEXES'] as $table_name => $indexes) {
  1306.                 if(isset($indexes['ChangedIndexes'])) {
  1307.                     $changedindexes = $indexes['ChangedIndexes'];
  1308.                     foreach($changedindexes as $index_name => $index) {
  1309.                         $result = $this->database->createIndex($table_name, $index_name,
  1310.                             $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name]);
  1311.                         if(MDB::isError($result)) {
  1312.                             break;
  1313.                         }
  1314.                         $alterations++;
  1315.                     }
  1316.                 }
  1317.                 if(!MDB::isError($result)
  1318.                     && isset($indexes['AddedIndexes'])
  1319.                 ) {
  1320.                     $addedindexes = $indexes['AddedIndexes'];
  1321.                     foreach($addedindexes as $index_name => $index) {
  1322.                         $result = $this->database->createIndex($table_name, $index_name,
  1323.                             $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name]);
  1324.                         if(MDB::isError($result)) {
  1325.                             break;
  1326.                         }
  1327.                         $alterations++;
  1328.                     }
  1329.                 }
  1330.                 if(MDB::isError($result)) {
  1331.                     break;
  1332.                 }
  1333.             }
  1334.         }
  1335.         if($alterations && MDB::isError($result)) {
  1336.             if($support_transactions) {
  1337.                 $res = $this->database->rollback();
  1338.                 if(MDB::isError($res))
  1339.                     $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1340.                         'Could not rollback the partially created database alterations ('
  1341.                         .$result->getMessage().' ('.$result->getUserinfo(),'))',
  1342.                         'MDB_Error', TRUE);
  1343.             } else {
  1344.                 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1345.                     'the requested database alterations were only partially implemented ('
  1346.                     .$result->getMessage().' ('.$result->getUserinfo(),'))',
  1347.                     'MDB_Error', TRUE);
  1348.             }
  1349.         }
  1350.         if($support_transactions) {
  1351.             $result = $this->database->autoCommit(TRUE);
  1352.             if(MDB::isError($result)) {
  1353.                 $result = $this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1354.                     'Could not end transaction after successfully implemented the requested database alterations ('
  1355.                     .$result->getMessage().' ('.$result->getUserinfo(),'))',
  1356.                     'MDB_Error', TRUE);
  1357.             }
  1358.         }
  1359.         $this->database->setDatabase($previous_database_name);
  1360.         return($result);
  1361.     }
  1362.  
  1363.     // }}}
  1364.     // {{{ _escapeSpecialCharacters()
  1365.  
  1366.     /**
  1367.      * add escapecharacters to all special characters in a string
  1368.      *
  1369.      * @param string $string string that should be escaped
  1370.      * @return string escaped string
  1371.      * @access private
  1372.      */
  1373.     function _escapeSpecialCharacters($string)
  1374.     {
  1375.         if(gettype($string) != 'string') {
  1376.             $string = strval($string);
  1377.         }
  1378.         for($escaped = '', $character = 0;
  1379.             $character < strlen($string);
  1380.             $character++)
  1381.         {
  1382.             switch($string[$character]) {
  1383.                 case '\"':
  1384.                 case '>':
  1385.                 case '<':
  1386.                 case '&':
  1387.                     $code = ord($string[$character]);
  1388.                     break;
  1389.                 default:
  1390.                     $code = ord($string[$character]);
  1391.                     if($code < 32 || $code>127) {
  1392.                         break;
  1393.                     }
  1394.                     $escaped .= $string[$character];
  1395.                     continue 2;
  1396.             }
  1397.             $escaped .= "&#$code;";
  1398.         }
  1399.         return($escaped);
  1400.     }
  1401.  
  1402.     // }}}
  1403.     // {{{ _dumpSequence()
  1404.  
  1405.     /**
  1406.      * dump the structure of a sequence
  1407.      *
  1408.      * @param string  $sequence_name
  1409.      * @param string  $eol
  1410.      * @return mixed string with xml seqeunce definition on success, or a MDB error object
  1411.      * @access private
  1412.      */
  1413.     function _dumpSequence($sequence_name, $eol, $dump = MDB_MANAGER_DUMP_ALL)
  1414.     {
  1415.         $sequence_definition = $this->database_definition['SEQUENCES'][$sequence_name];
  1416.         $buffer = "$eol <sequence>$eol  <name>$sequence_name</name>$eol";
  1417.         if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_CONTENT) {
  1418.             if(isset($sequence_definition['start'])) {
  1419.                 $start = $sequence_definition['start'];
  1420.                 $buffer .= "  <start>$start</start>$eol";
  1421.             }
  1422.         }
  1423.         if(isset($sequence_definition['on'])) {
  1424.             $buffer .= "  <on>$eol   <table>".$sequence_definition['on']['table']."</table>$eol   <field>".$sequence_definition['on']['field']."</field>$eol  </on>$eol";
  1425.         }
  1426.         $buffer .= " </sequence>$eol";
  1427.         return($buffer);
  1428.     }
  1429.  
  1430.     // }}}
  1431.     // {{{ parseDatabaseDefinitionFile()
  1432.  
  1433.     /**
  1434.      * Parse a database definition file by creating a Metabase schema format
  1435.      * parser object and passing the file contents as parser input data stream.
  1436.      *
  1437.      * @param string $input_file the path of the database schema file.
  1438.      * @param array $variables an associative array that the defines the text
  1439.      * string values that are meant to be used to replace the variables that are
  1440.      * used in the schema description.
  1441.      * @param bool $fail_on_invalid_names (optional) make function fail on invalid
  1442.      * names
  1443.      * @return mixed MDB_OK on success, or a MDB error object
  1444.      * @access public
  1445.      */
  1446.     function parseDatabaseDefinitionFile($input_file, $variables, $fail_on_invalid_names = 1)
  1447.     {
  1448.         $parser =& new MDB_Parser($variables, $fail_on_invalid_names);
  1449.         $result = $parser->setInputFile($input_file);
  1450.         if(MDB::isError($result)) {
  1451.             return($result);
  1452.         };
  1453.         $result = $parser->parse();
  1454.         if(MDB::isError($result)) {
  1455.             return($result);
  1456.         };
  1457.         if(MDB::isError($parser->error)) {
  1458.             return($parser->error);
  1459.         }
  1460.         return($parser->database_definition);
  1461.     }
  1462.  
  1463.     // }}}
  1464.     // {{{ _debugDatabaseChanges()
  1465.  
  1466.     /**
  1467.      * Dump the changes between two database definitions.
  1468.      *
  1469.      * @param array $changes an associative array that specifies the list
  1470.      * of database definitions changes as returned by the _compareDefinitions
  1471.      * manager class function.
  1472.      * @return mixed MDB_OK on success, or a MDB error object
  1473.      * @access private
  1474.      */
  1475.     function _debugDatabaseChanges($changes)
  1476.     {
  1477.         if(isset($changes['TABLES'])) {
  1478.             foreach($changes['TABLES'] as $table_name => $table)
  1479.             {
  1480.                 $this->database->debug("$table_name:");
  1481.                 if(isset($table['Add'])) {
  1482.                     $this->database->debug("\tAdded table '$table_name'");
  1483.                 } elseif(isset($table['Remove'])) {
  1484.                     $this->database->debug("\tRemoved table '$table_name'");
  1485.                 } else {
  1486.                     if(isset($table['name'])) {
  1487.                         $this->database->debug("\tRenamed table '$table_name' to '".$table['name']."'");
  1488.                     }
  1489.                     if(isset($table['AddedFields'])) {
  1490.                         foreach($table['AddedFields'] as $field_name => $field) {
  1491.                             $this->database->debug("\tAdded field '".$field_name."'");
  1492.                         }
  1493.                     }
  1494.                     if(isset($table['RemovedFields'])) {
  1495.                         foreach($table['RemovedFields'] as $field_name => $field) {
  1496.                             $this->database->debug("\tRemoved field '".$field_name."'");
  1497.                         }
  1498.                     }
  1499.                     if(isset($table['RenamedFields'])) {
  1500.                         foreach($table['RenamedFields'] as $field_name => $field) {
  1501.                             $this->database->debug("\tRenamed field '".$field_name."' to '".$field['name']."'");
  1502.                         }
  1503.                     }
  1504.                     if(isset($table['ChangedFields'])) {
  1505.                         foreach($table['ChangedFields'] as $field_name => $field) {
  1506.                             if(isset($field['type'])) {
  1507.                                 $this->database->debug(
  1508.                                     "\tChanged field '$field_name' type to '".$field['type']."'");
  1509.                             }
  1510.                             if(isset($field['unsigned'])) {
  1511.                                 $this->database->debug(
  1512.                                     "\tChanged field '$field_name' type to '".
  1513.                                     ($field['unsigned'] ? '' : 'not ')."unsigned'");
  1514.                             }
  1515.                             if(isset($field['length'])) {
  1516.                                 $this->database->debug(
  1517.                                     "\tChanged field '$field_name' length to '".
  1518.                                     ($field['length'] == 0 ? 'no length' : $field['length'])."'");
  1519.                             }
  1520.                             if(isset($field['ChangedDefault'])) {
  1521.                                 $this->database->debug(
  1522.                                     "\tChanged field '$field_name' default to ".
  1523.                                     (isset($field['default']) ? "'".$field['default']."'" : 'NULL'));
  1524.                             }
  1525.                             if(isset($field['ChangedNotNull'])) {
  1526.                                 $this->database->debug(
  1527.                                    "\tChanged field '$field_name' notnull to ".(isset($field['notnull']) ? "'1'" : '0'));
  1528.                             }
  1529.                         }
  1530.                     }
  1531.                 }
  1532.             }
  1533.         }
  1534.         if(isset($changes['SEQUENCES'])) {
  1535.             foreach($changes['SEQUENCES'] as $sequence_name => $sequence)
  1536.             {
  1537.                 $this->database->debug("$sequence_name:");
  1538.                 if(isset($sequence['Add'])) {
  1539.                     $this->database->debug("\tAdded sequence '$sequence_name'");
  1540.                 } elseif(isset($sequence['Remove'])) {
  1541.                     $this->database->debug("\tRemoved sequence '$sequence_name'");
  1542.                 } else {
  1543.                     if(isset($sequence['name'])) {
  1544.                         $this->database->debug("\tRenamed sequence '$sequence_name' to '".$sequence['name']."'");
  1545.                     }
  1546.                     if(isset($sequence['Change'])) {
  1547.                         foreach($sequence['Change'] as $sequence_name => $sequence) {
  1548.                             if(isset($sequence['start'])) {
  1549.                                 $this->database->debug(
  1550.                                     "\tChanged sequence '$sequence_name' start to '".$sequence['start']."'");
  1551.                             }
  1552.                         }
  1553.                     }
  1554.                 }
  1555.             }
  1556.         }
  1557.         if(isset($changes['INDEXES'])) {
  1558.             foreach($changes['INDEXES'] as $table_name => $table)
  1559.             {
  1560.                 $this->database->debug("$table_name:");
  1561.                 if(isset($table['AddedIndexes'])) {
  1562.                     foreach($table['AddedIndexes'] as $index_name => $index) {
  1563.                         $this->database->debug("\tAdded index '".$index_name."' of table '$table_name'");
  1564.                     }
  1565.                 }
  1566.                 if(isset($table['RemovedIndexes'])) {
  1567.                     foreach($table['RemovedIndexes'] as $index_name => $index) {
  1568.                         $this->database->debug("\tRemoved index '".$index_name."' of table '$table_name'");
  1569.                     }
  1570.                 }
  1571.                 if(isset($table['ChangedIndexes'])) {
  1572.                     foreach($table['ChangedIndexes'] as $index_name => $index) {
  1573.                         if(isset($index['name'])) {
  1574.                             $this->database->debug(
  1575.                                 "\tRenamed index '".$index_name."' to '".$index['name']."' on table '$table_name'");
  1576.                         }
  1577.                         if(isset($index['ChangedUnique'])) {
  1578.                             $this->database->debug(
  1579.                                 "\tChanged index '".$index_name."' unique to '".
  1580.                                 isset($index['unique'])."' on table '$table_name'");
  1581.                         }
  1582.                         if(isset($index['ChangedFields'])) {
  1583.                             $this->database->debug("\tChanged index '".$index_name."' on table '$table_name'");
  1584.                         }
  1585.                     }
  1586.                 }
  1587.             }
  1588.         }
  1589.         return(MDB_OK);
  1590.     }
  1591.  
  1592.     // }}}
  1593.     // {{{ _dumpDatabaseContents()
  1594.  
  1595.     /**
  1596.      * Parse a database schema definition file and dump the respective structure
  1597.      * and contents.
  1598.      *
  1599.      * @param string $schema_file path of the database schema file.
  1600.      * @param mixed $setup_arguments an associative array that takes pairs of tag names and values
  1601.      * that define the setup arguments that are passed to the
  1602.      * MDB_Manager::connect function.
  1603.      * @param array $dump_arguments an associative array that takes pairs of tag names and values
  1604.      * that define dump options as defined for the MDB_Manager::DumpDatabase
  1605.      * function.
  1606.      * @param array $variables an associative array that the defines the text string values
  1607.      * that are meant to be used to replace the variables that are used in the
  1608.      * schema description as defined for the
  1609.      * MDB_Manager::parseDatabaseDefinitionFile function.
  1610.      * @return mixed MDB_OK on success, or a MDB error object
  1611.      * @access private
  1612.      */
  1613.     function _dumpDatabaseContents($schema_file, $setup_arguments, $dump_arguments, $variables)
  1614.     {
  1615.         $database_definition = $this->parseDatabaseDefinitionFile($schema_file,
  1616.             $variables, $this->options['fail_on_invalid_names']);
  1617.         if(MDB::isError($database_definition)) {
  1618.             return($database_definition);
  1619.         }
  1620.         
  1621.         $this->database_definition = $database_definition;
  1622.         
  1623.         $result = $this->connect($setup_arguments);
  1624.         if(MDB::isError($result)) {
  1625.             return($result);
  1626.         }
  1627.         
  1628.         return($this->dumpDatabase($dump_arguments));
  1629.     }
  1630.  
  1631.     // }}}
  1632.     // {{{ getDefinitionFromDatabase()
  1633.  
  1634.     /**
  1635.      * Attempt to reverse engineer a schema structure from an existing MDB
  1636.      * This method can be used if no xml schema file exists yet.
  1637.      * The resulting xml schema file may need some manual adjustments.
  1638.      *
  1639.      * @return mixed MDB_OK or array with all ambiguities on success, or a MDB error object
  1640.      * @access public
  1641.      */
  1642.     function getDefinitionFromDatabase()
  1643.     {
  1644.         $database = $this->database->database_name;
  1645.         if(strlen($database) == 0) {
  1646.             return('it was not specified a valid database name');
  1647.         }
  1648.         $this->database_definition = array(
  1649.             'name' => $database,
  1650.             'create' => 1,
  1651.             'TABLES' => array()
  1652.         );
  1653.         $tables = $this->database->listTables();
  1654.         if(MDB::isError($tables)) {
  1655.             return($tables);
  1656.         }
  1657.         for($table = 0; $table < count($tables); $table++) {
  1658.             $table_name = $tables[$table];
  1659.             $fields = $this->database->listTableFields($table_name);
  1660.             if(MDB::isError($fields)) {
  1661.                 return($fields);
  1662.             }
  1663.             $this->database_definition['TABLES'][$table_name] = array('FIELDS' => array());
  1664.             for($field = 0; $field < count($fields); $field++)
  1665.             {
  1666.                 $field_name = $fields[$field];
  1667.                 $definition = $this->database->getTableFieldDefinition($table_name, $field_name);
  1668.                 if(MDB::isError($definition)) {
  1669.                     return($definition);
  1670.                 }
  1671.                 $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name] = $definition[0][0];
  1672.                 $field_choices = count($definition[0]);
  1673.                 if($field_choices > 1) {
  1674.                     $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): ";
  1675.                     $field_choice_cnt = 1;
  1676.                     $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name]['CHOICES'] = array();
  1677.                     foreach($definition[0] as $field_choice) {
  1678.                         $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name]['CHOICES'][] = $field_choice;
  1679.                         $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice);
  1680.                         $field_choice_cnt++;
  1681.                     }
  1682.                     $this->warnings[] = $warning;
  1683.                 }
  1684.                 if(isset($definition[1])) {
  1685.                     $sequence = $definition[1]['definition'];
  1686.                     $sequence_name = $definition[1]['name'];
  1687.                     $this->database->debug('Implicitly defining sequence: '.$sequence_name);
  1688.                     if(!isset($this->database_definition['SEQUENCES'])) {
  1689.                         $this->database_definition['SEQUENCES'] = array();
  1690.                     }
  1691.                     $this->database_definition['SEQUENCES'][$sequence_name] = $sequence;
  1692.                 }
  1693.                 if(isset($definition[2])) {
  1694.                     $index = $definition[2]['definition'];
  1695.                     $index_name = $definition[2]['name'];
  1696.                     $this->database->debug('Implicitly defining index: '.$index_name);
  1697.                     if(!isset($this->database_definition['TABLES'][$table_name]['INDEXES'])) {
  1698.                         $this->database_definition['TABLES'][$table_name]['INDEXES'] = array();
  1699.                     }
  1700.                     $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name] = $index;
  1701.                 }
  1702.             }
  1703.             $indexes = $this->database->listTableIndexes($table_name);
  1704.             if(MDB::isError($indexes)) {
  1705.                 return($indexes);
  1706.             }
  1707.             if(is_array($indexes) && count($indexes) > 0 && !isset($this->database_definition['TABLES'][$table_name]['INDEXES'])) {
  1708.                 $this->database_definition['TABLES'][$table_name]['INDEXES'] = array();
  1709.             }
  1710.             for($index = 0, $index_cnt = count($indexes); $index < $index_cnt; $index++)
  1711.             {
  1712.                 $index_name = $indexes[$index];
  1713.                 $definition = $this->database->getTableIndexDefinition($table_name, $index_name);
  1714.                 if(MDB::isError($definition)) {
  1715.                     return($definition);
  1716.                 }
  1717.                $this->database_definition['TABLES'][$table_name]['INDEXES'][$index_name] = $definition;
  1718.             }
  1719.             // ensure that all fields that have an index on them are set to not null
  1720.             if(isset($this->database_definition['TABLES'][$table_name]['INDEXES'])
  1721.                 && is_array($this->database_definition['TABLES'][$table_name]['INDEXES'])
  1722.                 && count($this->database_definition['TABLES'][$table_name]['INDEXES']) > 0
  1723.             ) {
  1724.                 foreach($this->database_definition['TABLES'][$table_name]['INDEXES'] as $index_check_null) {
  1725.                     foreach($index_check_null['FIELDS'] as $field_name_check_null => $field_check_null) {
  1726.                         $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_name_check_null]['notnull'] = 1;
  1727.                     }
  1728.                 }
  1729.             }
  1730.             // ensure that all fields that are set to not null also have a default value
  1731.             if(is_array($this->database_definition['TABLES'][$table_name]['FIELDS'])
  1732.                 && count($this->database_definition['TABLES'][$table_name]['FIELDS']) > 0
  1733.             ) {
  1734.                 foreach($this->database_definition['TABLES'][$table_name]['FIELDS'] as $field_set_default_name => $field_set_default) {
  1735.                     if(isset($field_set_default['notnull']) && $field_set_default['notnull']
  1736.                         && !isset($field_set_default['default'])
  1737.                     ) {
  1738.                         if(isset($this->default_values[$field_set_default['type']])) {
  1739.                             $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['default'] = $this->default_values[$field_set_default['type']];
  1740.                         } else {
  1741.                             $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['default'] = 0;
  1742.                         }
  1743.                     }
  1744.                     if(isset($field_set_default['CHOICES']) && is_array($field_set_default['CHOICES'])) {
  1745.                         foreach($field_set_default['CHOICES'] as $field_choices_set_default_name => $field_choices_set_default) {
  1746.                             if(isset($field_choices_set_default['notnull'])
  1747.                                 && $field_choices_set_default['notnull']
  1748.                                 && !isset($field_choices_set_default['default'])
  1749.                             ) {
  1750.                                 if(isset($this->default_values[$field_choices_set_default['type']])) {
  1751.                                     $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['CHOICES']
  1752.                                         [$field_choices_set_default_name]['default'] = $this->default_values[$field_choices_set_default['type']];
  1753.                                 } else {
  1754.                                     $this->database_definition['TABLES'][$table_name]['FIELDS'][$field_set_default_name]['CHOICES']
  1755.                                         [$field_choices_set_default_name]['default'] = 0;
  1756.                                 }
  1757.                             }
  1758.                         }
  1759.                     }
  1760.                 }
  1761.             }
  1762.         }
  1763.         $sequences = $this->database->listSequences();
  1764.         if(MDB::isError($sequences)) {
  1765.             return($sequences);
  1766.         }
  1767.         if(is_array($sequences) && count($sequences) > 0 && !isset($this->database_definition['SEQUENCES'])) {
  1768.             $this->database_definition['SEQUENCES'] = array();
  1769.         }
  1770.         for($sequence = 0; $sequence < count($sequences); $sequence++) {
  1771.             $sequence_name = $sequences[$sequence];
  1772.             $definition = $this->database->getSequenceDefinition($sequence_name);
  1773.             if(MDB::isError($definition)) {
  1774.                 return($definition);
  1775.             }
  1776.             $this->database_definition['SEQUENCES'][$sequence_name] = $definition;
  1777.         }
  1778.         return(MDB_OK);
  1779.     }
  1780.  
  1781.     // }}}
  1782.     // {{{ dumpDatabase()
  1783.  
  1784.     /**
  1785.      * Dump a previously parsed database structure in the Metabase schema
  1786.      * XML based format suitable for the Metabase parser. This function
  1787.      * may optionally dump the database definition with initialization
  1788.      * commands that specify the data that is currently present in the tables.
  1789.      *
  1790.      * @param array $arguments an associative array that takes pairs of tag
  1791.      * names and values that define dump options.
  1792.      *                 array (
  1793.      *                     'Definition'    =>    Boolean
  1794.      *                         TRUE   :  dump currently parsed definition
  1795.      *                         default:  dump currently connected database
  1796.      *                     'Output_Mode'    =>    String
  1797.      *                         'file' :   dump into a file
  1798.      *                         default:   dump using a function
  1799.      *                     'Output'        =>    String
  1800.      *                         depending on the 'Output_Mode'
  1801.      *                                  name of the file
  1802.      *                                  name of the function
  1803.      *                     'EndOfLine'        =>    String
  1804.      *                         end of line delimiter that should be used
  1805.      *                         default: "\n"
  1806.      *                 );
  1807.      * @param integer $dump constant that determines what data to dump
  1808.      *                      MDB_MANAGER_DUMP_ALL       : the entire db
  1809.      *                      MDB_MANAGER_DUMP_STRUCTURE : only the structure of the db
  1810.      *                      MDB_MANAGER_DUMP_CONTENT   : only the content of the db
  1811.      * @return mixed MDB_OK on success, or a MDB error object
  1812.      * @access public
  1813.      */
  1814.     function dumpDatabase($arguments, $dump = MDB_MANAGER_DUMP_ALL)
  1815.     {
  1816.         if(isset($arguments['Definition']) && $arguments['Definition']) {
  1817.             $dump_definition = TRUE;
  1818.         } else {
  1819.             if(!$this->database) {
  1820.                 return($this->raiseError(MDB_ERROR_NODBSELECTED,
  1821.                     NULL, NULL, 'please connect to a RDBMS first'));
  1822.             }
  1823.             $this->getDefinitionFromDatabase();
  1824.             $dump_definition = FALSE;
  1825.         }
  1826.         if(isset($arguments['Output'])) {
  1827.             if(isset($arguments['Output_Mode']) && $arguments['Output_Mode'] == 'file') {
  1828.                 $fp = fopen($arguments['Output'], 'w');
  1829.                 $output = FALSE;
  1830.             } elseif(function_exists($arguments['Output'])) {
  1831.                 $output = $arguments['Output'];
  1832.             } else {
  1833.                 return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1834.                         'no valid output function specified'));
  1835.             }
  1836.         } else {
  1837.             return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1838.                 'no output method specified'));
  1839.         }
  1840.         if(isset($arguments['EndOfLine'])) {
  1841.             $eol = $arguments['EndOfLine'];
  1842.         } else {
  1843.             $eol = "\n";
  1844.         }
  1845.         
  1846.         $sequences = array();
  1847.         if(isset($this->database_definition['SEQUENCES'])
  1848.             && is_array($this->database_definition['SEQUENCES'])
  1849.         ) {
  1850.             foreach($this->database_definition['SEQUENCES'] as $sequence_name => $sequence) {
  1851.                 if(isset($sequence['on'])) {
  1852.                     $table = $sequence['on']['table'];
  1853.                 } else {
  1854.                     $table = '';
  1855.                 }
  1856.                 $sequences[$table][] = $sequence_name;
  1857.             }
  1858.         }
  1859.         $previous_database_name = (strcmp($this->database_definition['name'], '') ? $this->database->setDatabase($this->database_definition['name']) : '');
  1860.         $buffer = ('<?xml version="1.0" encoding="ISO-8859-1" ?>'.$eol);
  1861.         $buffer .= ("<database>$eol$eol <name>".$this->database_definition['name']."</name>$eol <create>".$this->database_definition['create']."</create>$eol");
  1862.         
  1863.         if($output) {
  1864.             $output($buffer);
  1865.         } else {
  1866.             fwrite($fp, $buffer);
  1867.         }
  1868.         $buffer = '';
  1869.         if(isset($this->database_definition['TABLES']) && is_array($this->database_definition['TABLES'])) {
  1870.             foreach($this->database_definition['TABLES'] as $table_name => $table) {
  1871.                 $buffer = ("$eol <table>$eol$eol  <name>$table_name</name>$eol");
  1872.                 if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_STRUCTURE) {
  1873.                     $buffer .= ("$eol  <declaration>$eol");
  1874.                     if(isset($table['FIELDS']) && is_array($table['FIELDS'])) {
  1875.                         foreach($table['FIELDS'] as $field_name => $field) {
  1876.                             if(!isset($field['type'])) {
  1877.                                 return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  1878.                                     'it was not specified the type of the field "'.$field_name.'" of the table "'.$table_name));
  1879.                             }
  1880.                             $buffer .=("$eol   <field>$eol    <name>$field_name</name>$eol    <type>".$field['type']."</type>$eol");
  1881.                             if(in_array($field_name, array_keys($this->invalid_names))) {
  1882.                                 $this->warnings[] = "invalid field name: $field_name. You will need to set the class var \$fail_on_invalid_names to FALSE or change the field name.";
  1883.                             }
  1884.                             switch($field['type']) {
  1885.                                 case 'integer':
  1886.                                     if(isset($field['unsigned'])) {
  1887.                                         $buffer .=("    <unsigned>1</unsigned>$eol");
  1888.                                     }
  1889.                                     break;
  1890.                                 case 'text':
  1891.                                 case 'clob':
  1892.                                 case 'blob':
  1893.                                     if(isset($field['length'])) {
  1894.                                         $buffer .=('    <length>'.$field['length']."</length>$eol");
  1895.                                     }
  1896.                                     break;
  1897.                                 case 'boolean':
  1898.                                 case 'date':
  1899.                                 case 'timestamp':
  1900.                                 case 'time':
  1901.                                 case 'float':
  1902.                                 case 'decimal':
  1903.                                     break;
  1904.                                 default:
  1905.                                     return('type "'.$field['type'].'" is not yet supported');
  1906.                             }
  1907.                             if(isset($field['notnull'])) {
  1908.                                 $buffer .=("    <notnull>1</notnull>$eol");
  1909.                             }
  1910.                             if(isset($field['default'])) {
  1911.                                 $buffer .=('    <default>'.$this->_escapeSpecialCharacters($field['default'])."</default>$eol");
  1912.                             }
  1913.                             $buffer .=("   </field>$eol");
  1914.                         }
  1915.                     }
  1916.                     if(isset($table['INDEXES']) && is_array($table['INDEXES'])) {
  1917.                         foreach($table['INDEXES'] as $index_name => $index) {
  1918.                             $buffer .=("$eol   <index>$eol    <name>$index_name</name>$eol");
  1919.                             if(isset($index['unique'])) {
  1920.                                 $buffer .=("    <unique>1</unique>$eol");
  1921.                             }
  1922.                             foreach($index['FIELDS'] as $field_name => $field) {
  1923.                                 $buffer .=("    <field>$eol     <name>$field_name</name>$eol");
  1924.                                 if(is_array($field) && isset($field['sorting'])) { 
  1925.                                     $buffer .=('     <sorting>'.$field['sorting']."</sorting>$eol");
  1926.                                 }
  1927.                                 $buffer .=("    </field>$eol");
  1928.                             }
  1929.                             $buffer .=("   </index>$eol");
  1930.                         }
  1931.                     }
  1932.                     $buffer .= ("$eol  </declaration>$eol");
  1933.                 }
  1934.                 if($output) {
  1935.                     $output($buffer);
  1936.                 } else {
  1937.                     fwrite($fp, $buffer);
  1938.                 }
  1939.                 $buffer = '';
  1940.                 if($dump == MDB_MANAGER_DUMP_ALL || $dump == MDB_MANAGER_DUMP_CONTENT) {
  1941.                     if($dump_definition) {
  1942.                         if(isset($table['initialization']) && is_array($table['initialization'])) {
  1943.                             $buffer = ("$eol  <initialization>$eol");
  1944.                             foreach($table['initialization'] as $instruction_name => $instruction) {
  1945.                                 switch($instruction['type']) {
  1946.                                     case 'insert':
  1947.                                         $buffer .= ("$eol   <insert>$eol");
  1948.                                         foreach($instruction['FIELDS'] as $field_name => $field) {
  1949.                                             $buffer .= ("$eol    <field>$eol     <name>$field_name</name>$eol     <value>".$this->_escapeSpecialCharacters($field)."</value>$eol   </field>$eol");
  1950.                                         }
  1951.                                         $buffer .= ("$eol   </insert>$eol");
  1952.                                         break;
  1953.                                 }
  1954.                             }
  1955.                             $buffer .= ("$eol  </initialization>$eol");
  1956.                         }
  1957.                     } else {
  1958.                         $types = array();
  1959.                         foreach($table['FIELDS'] as $field) {
  1960.                             $types[] = $field['type'];
  1961.                         }
  1962.                         $query = 'SELECT '.implode(',',array_keys($table['FIELDS']))." FROM $table_name";
  1963.                         $result = $this->database->queryAll($query, $types, MDB_FETCHMODE_ASSOC);
  1964.                         if(MDB::isError($result)) {
  1965.                             return($result);
  1966.                         }
  1967.                         $rows = count($result);
  1968.                         if($rows > 0) {
  1969.                             $buffer = ("$eol  <initialization>$eol");
  1970.                             if($output) {
  1971.                                 $output($buffer);
  1972.                             } else {
  1973.                                 fwrite($fp, $buffer);
  1974.                             }
  1975.                             
  1976.                             for($row = 0; $row < $rows; $row++) {
  1977.                                 $buffer = ("$eol   <insert>$eol");
  1978.                                 $values = $result[$row];
  1979.                                 if(!is_array($values)) {
  1980.                                     break;
  1981.                                 } else {
  1982.                                     foreach($values as $field_name => $field) {
  1983.                                             $buffer .= ("$eol   <field>$eol     <name>$field_name</name>$eol     <value>");
  1984.                                             $buffer .= $this->_escapeSpecialCharacters($values[$field_name]);
  1985.                                             $buffer .= ("</value>$eol   </field>$eol");
  1986.                                     }
  1987.                                 }
  1988.                                 $buffer .= ("$eol   </insert>$eol");
  1989.                                 if($output) {
  1990.                                     $output($buffer);
  1991.                                 } else {
  1992.                                     fwrite($fp, $buffer);
  1993.                                 }
  1994.                                 $buffer = '';
  1995.                             }
  1996.                             $buffer = ("$eol  </initialization>$eol");
  1997.                             if($output) {
  1998.                                 $output($buffer);
  1999.                             } else {
  2000.                                 fwrite($fp, $buffer);
  2001.                             }
  2002.                             $buffer = '';
  2003.                         }
  2004.                     }
  2005.                 }
  2006.                 $buffer .= ("$eol </table>$eol");
  2007.                 if($output) {
  2008.                     $output($buffer);
  2009.                 } else {
  2010.                     fwrite($fp, $buffer);
  2011.                 }
  2012.                 if(isset($sequences[$table_name])) {
  2013.                     for($sequence = 0, $j = count($sequences[$table_name]);
  2014.                         $sequence < $j;
  2015.                         $sequence++)
  2016.                     {
  2017.                         $result = $this->_dumpSequence($sequences[$table_name][$sequence], $eol, $dump);
  2018.                         if(MDB::isError($result)) {
  2019.                             return($result);
  2020.                         }
  2021.                         if($output) {
  2022.                             $output($result);
  2023.                         } else {
  2024.                             fwrite($fp, $result);
  2025.                         }
  2026.                     }
  2027.                 }
  2028.             }
  2029.         }
  2030.         if(isset($sequences[''])) {
  2031.             for($sequence = 0;
  2032.                 $sequence < count($sequences['']);
  2033.                 $sequence++)
  2034.             {
  2035.                 $result = $this->_dumpSequence($sequences[''][$sequence], $eol, $dump);
  2036.                 if(MDB::isError($result)) {
  2037.                     return($result);
  2038.                 }
  2039.                 if($output) {
  2040.                        $output($result);
  2041.                    } else {
  2042.                        fwrite($fp, $result);
  2043.                 }
  2044.             }
  2045.         }
  2046.         
  2047.         $buffer = ("$eol</database>$eol");
  2048.         if($output) {
  2049.             $output($buffer);
  2050.         } else {
  2051.             fwrite($fp, $buffer);
  2052.             fclose($fp);
  2053.         }
  2054.         
  2055.         if(strcmp($previous_database_name, '')) {
  2056.             $this->database->setDatabase($previous_database_name);
  2057.         }
  2058.         return(MDB_OK);
  2059.     }
  2060.  
  2061.     // }}}
  2062.     // {{{ updateDatabase()
  2063.  
  2064.     /**
  2065.      * Compare the correspondent files of two versions of a database schema
  2066.      * definition: the previously installed and the one that defines the schema
  2067.      * that is meant to update the database.
  2068.      * If the specified previous definition file does not exist, this function
  2069.      * will create the database from the definition specified in the current
  2070.      * schema file.
  2071.      * If both files exist, the function assumes that the database was previously
  2072.      * installed based on the previous schema file and will update it by just
  2073.      * applying the changes.
  2074.      * If this function succeeds, the contents of the current schema file are
  2075.      * copied to replace the previous schema file contents. Any subsequent schema
  2076.      * changes should only be done on the file specified by the $current_schema_file
  2077.      * to let this function make a consistent evaluation of the exact changes that
  2078.      * need to be applied.
  2079.      *
  2080.      * @param string $current_schema_file name of the updated database schema
  2081.      * definition file.
  2082.      * @param string $previous_schema_file name the previously installed database
  2083.      * schema definition file.
  2084.      * @param array $variables an associative array that is passed to the argument
  2085.      * of the same name to the parseDatabaseDefinitionFile function. (there third
  2086.      * param)
  2087.      * @return mixed MDB_OK on success, or a MDB error object
  2088.      * @access public
  2089.      */
  2090.     function updateDatabase($current_schema_file, $previous_schema_file = FALSE, $variables = array())
  2091.     {
  2092.         $database_definition = $this->parseDatabaseDefinitionFile($current_schema_file,
  2093.             $variables, $this->options['fail_on_invalid_names']);
  2094.         if(MDB::isError($database_definition)) {
  2095.             return($database_definition);
  2096.         }
  2097.         $this->database_definition = $database_definition;
  2098.         $copy = 0;
  2099. /*
  2100.         $this->expectError(MDB_ERROR_UNSUPPORTED);
  2101.         $databases = $this->database->listDatabases();
  2102.         $this->popExpect();
  2103.         if((MDB::isError($databases) || (is_array($databases) && in_array($this->database_definition['name'], $databases)))
  2104.             && $previous_schema_file && file_exists($previous_schema_file))
  2105.         {
  2106. */
  2107.         if($previous_schema_file && file_exists($previous_schema_file)) {
  2108.             $previous_definition = $this->parseDatabaseDefinitionFile($previous_schema_file, $variables, 0);
  2109.             if(MDB::isError($previous_definition)) {
  2110.                 return($previous_definition);
  2111.             }
  2112.             $changes = $this->_compareDefinitions($previous_definition);
  2113.             if(MDB::isError($changes)) {
  2114.                 return($changes);
  2115.             }
  2116.             if(isset($changes) && is_array($changes)) {
  2117.                 $result = $this->_alterDatabase($previous_definition, $changes);
  2118.                 if(MDB::isError($result)) {
  2119.                     return($result);
  2120.                 }
  2121.                 $copy = 1;
  2122.                 if($this->options['debug']) {
  2123.                     $result = $this->_debugDatabaseChanges($changes);
  2124.                     if(MDB::isError($result)) {
  2125.                         return($result);
  2126.                     }
  2127.                 }
  2128.             }
  2129.         } else {
  2130.             $result = $this->_createDatabase();
  2131.             if(MDB::isError($result)) {
  2132.                 return($result);
  2133.             }
  2134.             $copy = 1;
  2135.         }
  2136.         if($copy && $previous_schema_file && !copy($current_schema_file, $previous_schema_file)) {
  2137.             return($this->raiseError(MDB_ERROR_MANAGER, NULL, NULL,
  2138.                 'Could not copy the new database definition file to the current file'));
  2139.         }
  2140.         return(MDB_OK);
  2141.     }
  2142.  
  2143.     // }}}
  2144. }
  2145. ?>
  2146.